﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using EzDesk.Apps.Data.Attendance;
using EzDesk.Apps.Model.Attendance;
using EzDesk.Utility.Extensions;
using EzDesk.Utility.Json;
using EzDesk.Utility.Log;
using EzDesk.Utility.ServiceStatusInfo;
using EzDesk.Utility;

namespace EzDesk.Apps.ServiceImpl.Attendance
{
    public partial class AttendanceService
    {
        #region [ private methods ]

        /// <summary>
        /// 考勤备注
        /// </summary>
        /// <param name="startDate"></param>
        /// <param name="endDate"></param>
        /// <param name="userIdList"></param>
        /// <param name="container"></param>
        /// <returns></returns>
        private List<DayAttendanceRemark> getAttendanceRemark(DateTime startDate, DateTime endDate, string memberstring)
        {
            return EzDeskAttendanceContainer.Go(
                container =>
                    {
                        var userIdList = _ezApiClient.GetSimpleUserInfos(memberstring).Select(p => p.Id);
                        var startDay = startDate.Date;
                        var endDay = endDate.Date;
                        var remarks = container.DayAttendanceRemarkSet
                            .Where(p => p.Date >= startDay && p.Date <= endDay)
                            .Where(p => userIdList.Contains(p.UserId));

                        return remarks.OrderBy(x => x.Date).ToList();
                    });
        }

        /// <summary>
        /// 更新考勤备注
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="day"></param>
        /// <param name="remark"></param>
        /// <returns></returns>
        private DayAttendanceRemark updateAttendanceRemarkInDay(Guid userId, DateTime day, string remark)
        {
            using (var container = new EzDeskAttendanceContainer())
            {
                container.ContextOptions.ProxyCreationEnabled = false;
                var remarkInfo =
                    container.DayAttendanceRemarkSet.FirstOrDefault(
                        dw => dw.Date == day.Date && dw.UserId == userId);
                if (remarkInfo == null)
                {
                    remarkInfo = new DayAttendanceRemark();
                    remarkInfo.Id = Guid.NewGuid();
                    remarkInfo.UserId = userId;
                    remarkInfo.Date = day.Date;
                    remarkInfo.Remark = remark;

                    container.DayAttendanceRemarkSet.AddObject(remarkInfo);
                }
                remarkInfo.Remark = remark;
                container.SaveChanges();

                return remarkInfo;
            }
        }

        private List<AttendanceInfo> getAttendanceInfoList(DateTime startDate, DateTime endDate, List<Guid> userIdList, AttendanceType? attendanceType,
            EzDeskAttendanceContainer container)
        {
            startDate = startDate.Date;
            endDate = endDate.Date;

            IQueryable<AttendanceInfo> query = container.AttendanceInfoSet;
            query = query.Where(p => p.Date >= startDate.Date && p.Date <= endDate.Date);
            if (attendanceType != null)
            {
                query = query.Where(p => p.AttendanceTypeValue == (int)attendanceType);
            }

            if (userIdList != null)
            {
                query = query.Where(p => userIdList.Contains(p.UserId));
            }

            return query.OrderBy(x => x.ActionTime).ToList();
        }

        private bool getIsNormalDuty(Guid userId, DateTime actionTime, AttendanceType attendanceType, ref bool isDuplicate, EzDeskAttendanceContainer container)
        {
            bool isNormalDuty = false;
            var normalAttendanceListToday = getAllNormalAttdanceInfo(userId, actionTime, container);
            int normalOnDutyCount =
                normalAttendanceListToday.Where(p => p.AttendanceType == AttendanceType.onDuty).Count();
            int normalOffDutyCount =
                normalAttendanceListToday.Where(p => p.AttendanceType == AttendanceType.offDuty).Count();
            if (attendanceType == AttendanceType.onDuty)
            {
                // 正常上班，当日没有其他正常下班
                if (normalOffDutyCount == 0)
                {
                    isNormalDuty = true;
                    if (normalOnDutyCount > 0)
                    {
                        isDuplicate = true;
                    }
                }
            }
            else if (attendanceType == AttendanceType.offDuty)
            {
                // 正常下班，当日已经有一次正常上班
                if (normalOnDutyCount > 0)
                {
                    isNormalDuty = true;
                    if (normalOffDutyCount > 0)
                    {
                        isDuplicate = true;
                    }
                }
            }
            return isNormalDuty;
        }

        private bool getIsInNormalTime(DateTime actionTime, AttendanceType attendanceType)
        {
            bool isInNormalTime = false;
            // 得到正常考勤时间段
            // 获得该次考勤的正常考勤时间段
            DateTime normalAttendanceStart;
            DateTime normalAttendanceEnd;
            getNormalAttendanceTimespan(
                attendanceType, actionTime.Date,
                out normalAttendanceStart,
                out normalAttendanceEnd
                );

            // 如果不在时间段内则直接属于非正常考勤
            if (actionTime >= normalAttendanceStart && actionTime <= normalAttendanceEnd)
            {
                isInNormalTime = true;
            }
            return isInNormalTime;
        }

        private bool getHasLeaveInfo(Guid userId, DateTime actionTime, EzDeskAttendanceContainer container)
        {
            return getLeaveInfoList(
                actionTime.Date,
                actionTime.Date.AddDays(1),
                null,
                null,
                userId,
                container,
                true,
                true).FirstOrDefault() != null;

        }



        /// <summary>
        /// 根据一个时间和考勤类型，获取在这个时间点发生的考勤默认情况是否为正常的考勤
        /// </summary>
        /// <param name="actionTime"></param>
        /// <param name="userId"></param>
        /// <param name="attendanceType"></param>
        /// <returns></returns>
        private AttendanceStatus getAttendanceStatus(
            Guid userId,
            DateTime actionTime,
            AttendanceType attendanceType,
            EzDeskAttendanceContainer container
            )
        {
            bool isAbnormal = true;
            bool isDuplicate = false;

            // 当日不需要上班则一定是非正常考勤
            var dayInfo = getDayInfo(actionTime.Date, userId, container);
            bool isNeedToWork = dayInfo.IsNeedToWork;

            // 当日是否有请假
            bool hasLeaveInfo = getHasLeaveInfo(userId, actionTime, container);

            bool isInNormalTime = getIsInNormalTime(actionTime, attendanceType);

            // 获得当日所有的正常考勤
            bool isNormalDuty = getIsNormalDuty(userId, actionTime, attendanceType, ref isDuplicate, container);

            isAbnormal = !isNeedToWork || hasLeaveInfo || !isInNormalTime || !isNormalDuty;

            var result = new AttendanceStatus()
            {
                AttendanceType = attendanceType,
                IsAbnormal = isAbnormal,
                IsDuplicated = isDuplicate,
            };

            return result;
        }

        /// <summary>
        /// 获取一个时间点上应该是上班还是下班，是否非正常考勤
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="actionTime"></param>
        private AttendanceStatus getDefaultAttendanceStatus(Guid userId, DateTime actionTime, EzDeskAttendanceContainer container)
        {
            // 默认考勤类型为 与当日上一次考勤类型相反的类型
            var date = DateTime.Now;
            AttendanceInfo attendanceInfo = getLatestAttendanceInDay(userId, date, container);
            // 默认为上班考勤
            AttendanceType defaultAttendanceType = AttendanceType.onDuty;

            // 前一次为上班考勤，则本次考勤默认为上班考勤
            if (attendanceInfo != null && attendanceInfo.AttendanceType == AttendanceType.onDuty)
            {
                defaultAttendanceType = AttendanceType.offDuty;
            }

            return getAttendanceStatus(userId, actionTime, defaultAttendanceType, container);
        }


        /// <summary>
        /// 获取所有正常考勤记录
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="actionTime"></param>
        /// <returns></returns>
        private IList<AttendanceInfo> getAllNormalAttdanceInfo(Guid userId, DateTime actionTime, EzDeskAttendanceContainer container)
        {
            var query = getAttendanceInfoList(actionTime.Date, actionTime.Date, convertIdToList(userId), null, container)
                .Where(p => p.IsAbnormalAttendance == false);

            return query.ToList();
        }

        /// <summary>
        /// 得到标准正常考勤时间的时间段
        /// </summary>
        /// <param name="attendanceType"></param>
        /// <param name="normalAttendanceStart"></param>
        /// <param name="normalAttendanceEnd"></param>
        private void getNormalAttendanceTimespan(
            AttendanceType attendanceType, DateTime date,
            out DateTime normalAttendanceStart, out DateTime normalAttendanceEnd
            )
        {
            // @Reflactor
            // 合法上班考勤时间
            // 标准上班时间向前2小时至当日中午12点
            var midToday = new DateTime(
                date.Year,
                date.Month,
                date.Day,
                12, 0, 0);
            DateTime midNight = date.Date.AddDays(1);

            if (attendanceType == AttendanceType.onDuty)
            {

                normalAttendanceStart =
                    attendanceConfig.StdOnDutyTime
                    .ToDateTime(date)
                    .Add(-attendanceConfig.OnDutyOffsetForward)
                    .Add(TimeSpan.FromHours(-2));

                normalAttendanceEnd = midToday;
            }
            else if (attendanceType == AttendanceType.offDuty)
            {

                normalAttendanceStart =
                    attendanceConfig.StdOffDutyTime
                    .ToDateTime(date)
                    .Add(-attendanceConfig.OnDutyOffsetForward)
                    .Add(TimeSpan.FromHours(-2));

                normalAttendanceEnd = midNight;
            }
            else
            {
                throw new ArgumentException("错误的考勤类型", "attendanceType");
            }
        }

        /// <summary>
        /// 获取当日最新一条考勤记录
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="date"></param>
        /// <returns></returns>
        private AttendanceInfo getLatestAttendanceInDay(Guid userId, DateTime date, EzDeskAttendanceContainer container)
        {
            return container.AttendanceInfoSet
                .Where(p => p.UserId == userId && p.Date == date)
                .OrderByDescending(p => p.ActionTime)
                .FirstOrDefault();
        }


        /// <summary>
        /// 添加一条考勤记录
        /// </summary>
        /// <param name="attendanceInfo"></param>
        private void addAttendanceInfo(AttendanceInfo attendanceInfo, EzDeskAttendanceContainer container)
        {
            container.AttendanceInfoSet.AddObject(attendanceInfo);
            container.SaveChanges();
        }

        /// <summary>
        /// 将一条考勤记录设置为非正常考勤
        /// </summary>
        /// <param name="attendanceInfo"></param>
        private void setNormalAttendanceInfoToAbnormal(AttendanceInfo attendanceInfo, EzDeskAttendanceContainer container)
        {
            container.AttendanceInfoSet.Attach(attendanceInfo);
            attendanceInfo.IsAbnormalAttendance = true;
            container.SaveChanges();
        }

        private string getAttenanceTypeDisplayText(AttendanceType attendanceType)
        {
            var attenanceTypeDisplayText = "";
            if (attendanceType == AttendanceType.onDuty)
            {
                attenanceTypeDisplayText = "上班";
            }
            if (attendanceType == AttendanceType.offDuty)
            {
                attenanceTypeDisplayText = "下班";
            }
            return attenanceTypeDisplayText;
        }
        #region [ feed methods ]

        /// <summary>
        /// 考勤成功发送给自己一条FEED
        /// </summary>
        /// <param name="actionTime"></param>
        /// <param name="attendanceType"></param>
        /// <param name="userId"></param>
        private void sendChecckedAttendanceFeed(DateTime actionTime, AttendanceType attendanceType, Guid userId)
        {

            var dateTimeStr = actionTime.ToFullDateTime();
            var typeStr = getAttenanceTypeDisplayText(attendanceType);

            var feedData = new Dictionary<string, string>
                               {
                                   {"dateTimeStr",dateTimeStr},
                                   {"typeStr", typeStr},
                                   {"dateEpoch", actionTime.Date.ToEpoch().ToString() },
                               };
            //发送给自己
            string memberString = userId.ToString();

            _ezApiClient.SendFeed(
                FeedTemplateStr_attendance_checkedAttendance,
                JsonHelper.ConvertToJson(feedData),
                memberString
                );
        }
        #endregion

        #endregion



        #region [about Attendance Remark]

        /// <summary>
        /// 获取一个员工的考勤备注
        /// </summary>
        /// <param name="startDate"></param>
        /// <param name="endDate"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public StatusInfo<List<DayAttendanceRemark>> GetAttendanceRemarkListByUserBetweenDays(
           DateTime startDate,
           DateTime endDate,
           Guid userId)
        {
            return EzDeskAttendanceContainer.Go(
                container => StatusInfo.Wrap(
                    () => getAttendanceRemark(startDate, endDate, userId.ToString())));
        }


        /// <summary>
        /// 获取一个员工某天的考勤备注
        /// </summary>
        /// <param name="day"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public StatusInfo<List<DayAttendanceRemark>> GetAttendanceRemarkListByUserInDay(
          DateTime day,
          Guid userId)
        {
            return EzDeskAttendanceContainer.Go(
              container => StatusInfo.Wrap(
                  () => getAttendanceRemark(day, day, userId.ToString())));
        }

        public StatusInfo<List<DayAttendanceRemark>> GetAttendanceRemarkListByUsersInDay(DateTime day, List<Guid> userIdList)
        {
            return EzDeskAttendanceContainer.Go(
              container => StatusInfo.Wrap(
                  () => getAttendanceRemark(day, day, userIdList.Join(","))));
        }


        ///// <summary>
        ///// 获取一批员工一天的考勤备注
        ///// </summary>
        ///// <param name="day"></param>
        ///// <param name="userIdList"></param>
        ///// <returns></returns>
        //public StatusInfo<List<DayAttendanceRemark>> GetAttendanceRemarkListByUsersInDay(
        //   DateTime day,
        //   List<Guid> userIdList)
        //{
        //    return EzDeskAttendanceContainer.Go(
        //        container => StatusInfo.Wrap(
        //            () => getAttendanceRemark(day, day, userIdList, container)));
        //}


        /// <summary>
        /// 更新remark
        /// </summary>
        /// <param name="attendanceRemark"></param>
        /// <returns></returns>
        public StatusInfo<DayAttendanceRemark> UpdateAttendanceRemarkInDay(Guid userId, DateTime day, string remark)
        {
            return StatusInfo.Wrap(
                () => updateAttendanceRemarkInDay(userId, day, remark));
        }


        #endregion



        #region [ about AttendanceInfo ]


        public StatusInfo<AttendanceStatus> GetDefaultAttendanceStatus(Guid userId, DateTime time)
        {
            return StatusInfo.Wrap(
                () => EzDeskAttendanceContainer.Go(
                    container => getDefaultAttendanceStatus(userId, time, container))
                );
        }

        /// <summary>
        /// 返回某种考勤类型的状态
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="actionTime"></param>
        /// <param name="attendanceInfo"></param>
        /// <returns></returns>
        public StatusInfo<AttendanceStatus> GetAttendanceStatus(Guid userId, DateTime actionTime, AttendanceType attendanceInfo)
        {
            return StatusInfo.Wrap(
                () => EzDeskAttendanceContainer.Go(
                    container =>
                    getAttendanceStatus(userId, actionTime, attendanceInfo, container)
                          ));
        }

        /// <summary>
        /// 进行一次考勤
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="actionTime"></param>
        /// <param name="attendanceType"></param>
        /// <param name="isAbnormal"></param>
        /// <param name="explaination"></param>
        public StatusInfo CheckAttendance(
            Guid userId,
            DateTime actionTime,
            AttendanceType attendanceType,
            string explaination
            )
        {
            return StatusInfo.Wrap(
                () => EzDeskAttendanceContainer.Go(
                    container =>
                    {
                        var statusInfo = getAttendanceStatus(userId, actionTime, attendanceType, container);

                        // 上一次考勤是正常上班，这一次是非正常下班，则将上一次考勤也变成非正常上班
                        var lastAttendanceInfo = getLatestAttendanceInDay(userId, actionTime.Date, container);
                        if (lastAttendanceInfo != null &&
                            lastAttendanceInfo.AttendanceType == AttendanceType.onDuty &&
                            !lastAttendanceInfo.IsAbnormalAttendance
                            )
                        {
                            if (statusInfo.IsAbnormal)
                            {
                                setNormalAttendanceInfoToAbnormal(lastAttendanceInfo, container);
                            }
                        }
                        // 上一次考勤是正常下班

                        if (statusInfo.IsDuplicated)
                        {
                            throw new Exception("重复考勤了，考勤没有成功");
                        }

                        var attendanceInfo = new AttendanceInfo()
                                                 {
                                                     Id = Guid.NewGuid(),
                                                     ActionTime = actionTime,
                                                     AttendanceType = attendanceType,
                                                     Date = actionTime.Date,
                                                     Explanation = explaination,
                                                     IsAbnormalAttendance = statusInfo.IsAbnormal,
                                                     UserId = userId,
                                                 };

                        addAttendanceInfo(attendanceInfo, container);


                        if(!string.IsNullOrEmpty(explaination) )
                        {
                            var currentRemark =
                                getAttendanceRemark(actionTime.Date, actionTime.Date, userId.ToString()).FirstOrDefault();
                            var sb = new StringBuilder();
                            if (currentRemark != null)
                            {
                                sb.AppendLine(currentRemark.Remark);
                            }
                            
                            // todo: 多语言问题
                            sb.AppendFormat("{0} {1}: {2}", actionTime.ToFullTime(),
                                            attendanceType == AttendanceType.onDuty ? "上班" : "下班", explaination);

                            // 如果考勤备注不为空，那么将考勤备注加入当天考勤备注中
                            updateAttendanceRemarkInDay(userId, actionTime.Date, sb.ToString());
                        }


                        // 考勤完成后，刷新AttendanceEvaluation
                        refreshDayAttendanceEvaluation(userId, actionTime.Date, container);

                        //考勤成功发送feed
                        sendChecckedAttendanceFeed(actionTime, attendanceType, userId);

                    })
                );
        }



        #region [ GetAttendanceListInfoList and Overloads ]
        /// <summary>
        /// 根据考勤类型的日期查询考勤记录
        /// 查询结果将包括开始时间和结束时间
        /// </summary>
        /// <param name="startDate">查询考勤记录的开始时间</param>
        /// <param name="endDate">查询考勤记录的结束时间</param>
        /// <param name="attendanceType">查询的考勤类型，为null则返回任意类型</param>
        /// <param name="userIdList">查询的用户列表名单，为null则返回符合条件的所有用户考勤记录</param>
        /// <returns></returns>
        public StatusInfo<List<AttendanceInfo>> GetAttendanceInfoListByUsersBetweenDays(
            DateTime startDate,
            DateTime endDate,
            List<Guid> userIdList = null,
            AttendanceType? attendanceType = null)
        {
            return EzDeskAttendanceContainer.Go(
                container => StatusInfo.Wrap(
                    () => getAttendanceInfoList(startDate, endDate, userIdList, attendanceType, container)));
        }


        /// <summary>
        /// 根据考勤类型的日期查询考勤记录
        /// 查询结果将包括开始时间和结束时间
        /// </summary>
        /// <param name="startDate">查询考勤记录的开始时间</param>
        /// <param name="endDate">查询考勤记录的结束时间</param>
        /// <param name="userId">查询的用户ID</param>
        /// <param name="attendanceType">查询的考勤类型，为null则返回任意类型</param>
        /// <returns></returns>
        public StatusInfo<List<AttendanceInfo>> GetAttendanceInfoListByUserBetweenDays(
            DateTime startDate,
            DateTime endDate,
            Guid userId,
            AttendanceType? attendanceType = null)
        {
            LogEx.TRACE("AttendanceService - GetAttendanceInfoListByUserBetweenDays", "请求拿一个用户一个月的AttendanceInfo");
            var result = GetAttendanceInfoListByUsersBetweenDays(startDate, endDate, convertIdToList(userId),
                                                            attendanceType);
            LogEx.TRACE("AttendanceService - GetAttendanceInfoListByUserBetweenDays", "拿一个用户一个月的AttendanceInfo结束");
            return result;
        }

        /// <summary>
        /// 根据考勤类型的日期查询考勤记录
        /// </summary>
        /// <param name="day">查询考勤记录的日期</param>
        /// <param name="attendanceType">查询的考勤类型，为null则返回任意类型</param>
        /// <param name="userIdList">查询的用户列表名单，为null则返回符合条件的所有用户考勤记录</param>
        /// <returns></returns>
        public StatusInfo<List<AttendanceInfo>> GetAttendanceInfoListByUsersInDay(
            DateTime day,
            List<Guid> userIdList = null,
            AttendanceType? attendanceType = null)
        {
            return GetAttendanceInfoListByUsersBetweenDays(day, day, userIdList, attendanceType);
        }

        /// <summary>
        /// 根据考勤类型的日期查询考勤记录
        /// </summary>
        /// <param name="day">查询考勤记录的日期</param>
        /// <param name="userId">查询的用户ID</param>
        /// <param name="attendanceType">查询的考勤类型，为null则返回任意类型</param>
        /// <returns></returns>
        public StatusInfo<List<AttendanceInfo>> GetAttendanceInfoListByUserInDay(
            DateTime day,
            Guid userId,
            AttendanceType? attendanceType = null)
        {
            return GetAttendanceInfoListByUsersBetweenDays(day, day, convertIdToList(userId), attendanceType);
        }

        private List<Guid> convertIdToList(Guid id)
        {
            return new List<Guid> { id };
        }


        #endregion


        #endregion
    }
}
